package com.krtech.wecard.module.oauth2;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.oauth2.model.*;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import com.krtech.wecard.framework.exception.BusinessException;
import com.krtech.wecard.framework.model.CommRes;
import com.krtech.wecard.framework.utils.LoginUtil;
import com.krtech.wecard.module.oauth2.vo.ClientSecretVo;
import com.krtech.wecard.module.sys.entity.SysUser;
import lombok.extern.slf4j.Slf4j;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;

@Slf4j
public class WecardOAUTH2Handle {

    /**
     * 处理Server端请求， 路由分发ssss
     * @return 处理结果
     */
    public static Object serverRequest(String domain,String mipOauth2Direct, List<ClientSecretVo> clientAndSecretList) {

        // 获取变量
        SaRequest req = SaHolder.getRequest();
        SaResponse res = SaHolder.getResponse();
        SaOAuth2Config cfg = SaOAuth2Manager.getConfig();

        //判断是否登陆
        String wecard = req.getParam("wecard");

        log.info("oauth2认证的token:"+wecard);

        if (StrUtil.isNotEmpty(wecard) && !wecard.equals("null")){
            try {
                Object loginIdByToken = StpUtil.getLoginIdByToken(wecard);
                StpUtil.login(loginIdByToken);
            }catch (Exception e){
                e.printStackTrace();
                // 重定向到页面，让页面重定向到公众号处理
                res.redirect(mipOauth2Direct);
            }


        }else if (wecard != null && wecard.equals("null")){
            //已经失败了，如果failRedirectUrl不为空，就跳转
            if (StrUtil.isNotEmpty(req.getParam("failRedirectUrl")) && !"null".equals(req.getParam("failRedirectUrl"))){
                res.redirect(req.getParam("failRedirectUrl"));
            }else {
                //跳转到当前得domin
                // 重定向

                res.redirect(mipOauth2Direct);
            }
        }

        // ------------------ 路由分发 ------------------

        // 模式一：Code授权码
        if(req.isPath(SaOAuth2Consts.Api.authorize) && req.isParam(SaOAuth2Consts.Param.response_type, SaOAuth2Consts.ResponseType.code)) {
            SaClientModel cm = currClientModel();
            if(cfg.isCode && (cm.isCode || cm.isAutoMode)) {
                return authorize(req, res, cfg,mipOauth2Direct);
            }
            throw new SaOAuth2Exception("暂未开放的授权模式");
        }

        // Code授权码 获取 Access-Token
        if(req.isPath(SaOAuth2Consts.Api.token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.authorization_code)) {
            return token(req, res, cfg,clientAndSecretList);
        }

        // Refresh-Token 刷新 Access-Token
        if(req.isPath(SaOAuth2Consts.Api.refresh) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.refresh_token)) {
            return refreshToken(req);
        }

        // 回收 Access-Token
        if(req.isPath(SaOAuth2Consts.Api.revoke)) {
            return revokeToken(req);
        }

        // doLogin 登录接口
        if(req.isPath(SaOAuth2Consts.Api.doLogin)) {
            return doLogin(req, res, cfg);
        }

        // doConfirm 确认授权接口
        if(req.isPath(SaOAuth2Consts.Api.doConfirm)) {
            return doConfirm(req);
        }
        // userinfo,获取用户数据借口
        if(req.isPath("/oauth2/userInfo")) {
            String access_token = req.getParam("access_token");
            Object loginId = SaOAuth2Util.getLoginIdByAccessToken(access_token);
            SysUser userInfo = LoginUtil.getUserInfo(Long.parseLong(loginId.toString()));
            return CommRes.ok(userInfo);
        }

        // 模式二：隐藏式
        if(req.isPath(SaOAuth2Consts.Api.authorize) && req.isParam(SaOAuth2Consts.Param.response_type, SaOAuth2Consts.ResponseType.token)) {
            SaClientModel cm = currClientModel();
            if(cfg.isImplicit && (cm.isImplicit || cm.isAutoMode)) {
                return authorize(req, res, cfg,mipOauth2Direct);
            }
            throw new SaOAuth2Exception("暂未开放的授权模式");
        }

        // 模式三：密码式
        if(req.isPath(SaOAuth2Consts.Api.token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.password)) {
            SaClientModel cm = currClientModel();
            if(cfg.isPassword && (cm.isPassword || cm.isAutoMode)) {
                return password(req, res, cfg);
            }
            throw new SaOAuth2Exception("暂未开放的授权模式");
        }

        // 模式四：凭证式
        if(req.isPath(SaOAuth2Consts.Api.client_token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.client_credentials)) {
            SaClientModel cm = currClientModel();
            if(cfg.isClient && (cm.isClient || cm.isAutoMode)) {
                return clientToken(req, res, cfg);
            }
            throw new SaOAuth2Exception("暂未开放的授权模式");
        }

        // 默认返回
        return SaOAuth2Consts.NOT_HANDLE;
    }

    /**
     * 模式一：Code授权码 / 模式二：隐藏式
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    public static Object authorize(SaRequest req, SaResponse res, SaOAuth2Config cfg,String mipOauth2Direct) {

        String response_type = req.getParam("response_type");
        String client_id = req.getParam("client_id");
        String redirect_uri = req.getParam("redirect_uri");
        String state = req.getParam("state");
        String failRedirectUrl = req.getParam("failRedirectUrl");


        log.info("OAuth2判断是否登录");
        // 1、如果尚未登录, 则先去登录
        if(StpUtil.isLogin() == false) {
            cfg.setNotLoginView(() -> {
                String msg = "";
                if (StrUtil.isNotEmpty(failRedirectUrl)){
                    msg = "<script >window.location.href = \""+mipOauth2Direct+"?" +
                            "response_type="+response_type+"&client_id="+client_id+"&redirect_uri="+URLEncoder.encode(redirect_uri)+"&state="+ URLEncoder.encode(state)+"&failRedirectUrl="+URLEncoder.encode(failRedirectUrl)+"\"</script>";
                }else {
                    msg = "<script >window.location.href = \""+mipOauth2Direct+"?" +
                            "response_type="+response_type+"&client_id="+client_id+"&redirect_uri="+URLEncoder.encode(redirect_uri)+"&state="+ URLEncoder.encode(state)+"\"</script>";
                }
//                    String msg = "当前会话在SSO-Server端尚未登录，请先访问"
//                            + "<a href='/oauth2/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
//                            + "进行登录之后，刷新页面开始授权";

             return msg;
            });
            log.info("OAuth2未登录");
            return cfg.notLoginView.get();
        };

        log.info("OAuth2已登录");
        // 2、构建请求Model
        RequestAuthModel ra = SaOAuth2Util.generateRequestAuth(req, StpUtil.getLoginId());

        // 3、校验：重定向域名是否合法
        SaOAuth2Util.checkRightUrl(ra.clientId, ra.redirectUri);

        // 4、校验：此次申请的Scope，该Client是否已经签约
        SaOAuth2Util.checkContract(ra.clientId, ra.scope);

        // 5、判断：如果此次申请的Scope，该用户尚未授权，则转到授权页面
        boolean isGrant = SaOAuth2Util.isGrant(ra.loginId, ra.clientId, ra.scope);
        if(isGrant == false) {
            return cfg.confirmView.apply(ra.clientId, ra.scope);
        }

        // 6、判断授权类型
        // 如果是 授权码式，则：开始重定向授权，下放code
        if(SaOAuth2Consts.ResponseType.code.equals(ra.responseType)) {
            CodeModel codeModel = SaOAuth2Util.generateCode(ra);

            log.info("ra:"+ra.toString());

            String uri = ra.redirectUri;
            if (StrUtil.isNotEmpty(uri) && uri.contains("amp;") && uri.contains("&")){
                uri = URLDecoder.decode(ra.redirectUri).replace("amp;", "");
            }

            String encodeState = ra.state;
            if (StrUtil.isNotEmpty(encodeState) && encodeState.contains("amp;") && encodeState.contains("&")){
                encodeState = URLDecoder.decode(ra.state).replace("amp;", "");
            }

            String redirectUri = SaOAuth2Util.buildRedirectUri(uri, codeModel.code, URLEncoder.encode(encodeState));

            if (StrUtil.isNotEmpty(redirectUri) && redirectUri.contains("#")){
                redirectUri = redirectUri.replace("#", URLEncoder.encode("#"));
            }
//            redirectUri.substring(0,redirectUri.indexOf("?")+1) + redirectUri.

//            String s = TruncateUrlPage(redirectUri);


            log.info("OAuth2进行下发code重定向，重定向地址:"+redirectUri);
            return res.redirect(redirectUri);
        }

        // 如果是 隐藏式，则：开始重定向授权，下放 token
        if(SaOAuth2Consts.ResponseType.token.equals(ra.responseType)) {
            AccessTokenModel at = SaOAuth2Util.generateAccessToken(ra, false);
            String redirectUri = SaOAuth2Util.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
            return res.redirect(redirectUri);
        }

        // 默认返回
        throw new SaOAuth2Exception("无效response_type: " + ra.responseType);
    }

    private static String TruncateUrlPage(String redirectUri) {
        String uri = redirectUri.substring(0,redirectUri.indexOf("?") + 1);
        String params = redirectUri.substring(redirectUri.indexOf("?") + 1);

        String paramsUri = params.substring(0,params.indexOf("?") + 1);
        String paramParams = params.substring(params.indexOf("?") + 1);
        Map<String, String> map = HttpUtil.decodeParamMap(paramParams, "UTF-8");
        map.keySet().stream().forEach(one->{
            if (!"code".equals(one)){
                map.put(one,URLEncoder.encode(map.get(one)));
            }
        });
        String encodeUri = uri +paramsUri + HttpUtil.toParams(map);
        return encodeUri;
    }

    /**
     * Code授权码 获取 Access-Token
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    public static Object token(SaRequest req, SaResponse res, SaOAuth2Config cfg,List<ClientSecretVo> clientAndSecretList) {
        log.info("OAuth2 code换token");
        // 获取参数
        String code = req.getParamNotNull(SaOAuth2Consts.Param.code);
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(SaOAuth2Consts.Param.client_secret);
        String redirectUri = req.getParam(SaOAuth2Consts.Param.redirect_uri);

        // 校验参数
        SaOAuth2Util.checkGainTokenParam(code, clientId, clientSecret, redirectUri);

        boolean flag = false;
        //校验clientId和secretId是否合法
        if (clientAndSecretList.size() > 0){
            for (ClientSecretVo clientSecretVo:clientAndSecretList){
                if (clientSecretVo.getClientId().equals(clientId) && clientSecretVo.getSecretId().equals(clientSecret)){
                    flag = true;
                }
            }
        }
        if (!flag){
            throw new BusinessException("ClientId和SecretId校验不合法");
        }

        // 构建 Access-Token
        AccessTokenModel token = SaOAuth2Util.generateAccessToken(code);

        // 返回
        log.info("OAuth2 code换token结束");
        return SaResult.data(token.toLineMap());
    }

    /**
     * Refresh-Token 刷新 Access-Token
     * @param req 请求对象
     * @return 处理结果
     */
    public static Object refreshToken(SaRequest req) {
        // 获取参数
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(SaOAuth2Consts.Param.client_secret);
        String refreshToken = req.getParamNotNull(SaOAuth2Consts.Param.refresh_token);

        // 校验参数
        SaOAuth2Util.checkRefreshTokenParam(clientId, clientSecret, refreshToken);

        // 获取新Token返回
        Object data = SaOAuth2Util.refreshAccessToken(refreshToken).toLineMap();
        return SaResult.data(data);
    }

    /**
     * 回收 Access-Token
     * @param req 请求对象
     * @return 处理结果
     */
    public static Object revokeToken(SaRequest req) {
        // 获取参数
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(SaOAuth2Consts.Param.client_secret);
        String accessToken = req.getParamNotNull(SaOAuth2Consts.Param.access_token);

        // 如果 Access-Token 不存在，直接返回
        if(SaOAuth2Util.getAccessToken(accessToken) == null) {
            return SaResult.ok("access_token不存在：" + accessToken);
        }

        // 校验参数
        SaOAuth2Util.checkAccessTokenParam(clientId, clientSecret, accessToken);

        // 获取新Token返回
        SaOAuth2Util.revokeAccessToken(accessToken);
        return SaResult.ok();
    }

    /**
     * doLogin 登录接口
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    public static Object doLogin(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
        return cfg.doLoginHandle.apply(req.getParamNotNull(SaOAuth2Consts.Param.name), req.getParamNotNull("pwd"));
    }

    /**
     * doConfirm 确认授权接口
     * @param req 请求对象
     * @return 处理结果
     */
    public static Object doConfirm(SaRequest req) {
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String scope = req.getParamNotNull(SaOAuth2Consts.Param.scope);
        Object loginId = StpUtil.getLoginId();
        SaOAuth2Util.saveGrantScope(clientId, loginId, scope);
        return SaResult.ok();
    }

    /**
     * 模式三：密码式
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    public static Object password(SaRequest req, SaResponse res, SaOAuth2Config cfg) {

        // 1、获取请求参数
        String username = req.getParamNotNull(SaOAuth2Consts.Param.username);
        String password = req.getParamNotNull(SaOAuth2Consts.Param.password);
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String scope = req.getParam(SaOAuth2Consts.Param.scope, "");

        // 2、校验 ClientScope
        SaOAuth2Util.checkContract(clientId, scope);

        // 3、防止因前端误传token造成逻辑干扰
        SaHolder.getStorage().set(StpUtil.stpLogic.splicingKeyJustCreatedSave(), "no-token");

        // 4、调用API 开始登录，如果没能成功登录，则直接退出
        Object retObj = cfg.doLoginHandle.apply(username, password);
        if(StpUtil.isLogin() == false) {
            return retObj;
        }

        // 5、构建 ra对象
        RequestAuthModel ra = new RequestAuthModel();
        ra.clientId = clientId;
        ra.loginId = StpUtil.getLoginId();
        ra.scope = scope;

        // 7、生成 Access-Token
        AccessTokenModel at = SaOAuth2Util.generateAccessToken(ra, true);

        // 8、返回 Access-Token
        return SaResult.data(at.toLineMap());
    }

    /**
     * 模式四：凭证式
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    public static Object clientToken(SaRequest req, SaResponse res, SaOAuth2Config cfg) {

        // 获取参数
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(SaOAuth2Consts.Param.client_secret);
        String scope = req.getParam(SaOAuth2Consts.Param.scope);

        //校验 ClientScope
        SaOAuth2Util.checkContract(clientId, scope);

        // 校验 ClientSecret
        SaOAuth2Util.checkClientSecret(clientId, clientSecret);

        // 返回 Client-Token
        ClientTokenModel ct = SaOAuth2Util.generateClientToken(clientId, scope);

        // 返回 Client-Token
        return SaResult.data(ct.toLineMap());
    }

    /**
     * 根据当前请求提交的 client_id 参数获取 SaClientModel 对象
     * @return /
     */
    public static SaClientModel currClientModel() {
        String clientId = SaHolder.getRequest().getParam(SaOAuth2Consts.Param.client_id);
        return SaOAuth2Util.checkClientModel(clientId);
    }

}
